#include <iostream>
#include <fstream>
#include <sys/stat.h>
#include <sys/types.h>

using namespace std;

#include <project.h>

using namespace ProjectMaker;

void showUsage() {
	cout << "usage: project-maker <commands> arguments" << std::endl
		<< "where <commands> are:" << std::endl << "\t--project-name=NAME\t\t"
		<< "Name of the project. This command is mandatory." << std::endl
		<< "\t--project-version=VERSION\tSpecify the project version."
		<< std::endl << "\t--project-language=LANG\t\tProject language [c, cpp]."
		<< std::endl << "\t--output-dir=DIR\t\tWhere the project should be created."
		<< std::endl;
}

enum ParsedValues {
	Name = 0x001, ProjVer = 0x002, ProjLang = 0x004, ProjDir = 0x008
};

typedef struct ParsingResult {
	int parsedValues;
	string outDir;
} ParsingResult;

string dirs[12] = {"bin", "bin/debug", "bin/release", "dependencies",
	"dependencies/include", "dependencies/lib", "include", "obj",
	"obj/debug", "obj/release", "scripts", "src" };

void parseLanguage(const string& language, Project* project) {
	if (language == "c") {
		project->setLanguage(LanguageType::C);
	} else if (language == "cpp") {
		project->setLanguage(LanguageType::CPP);
	} else {
		cout << "Unknown language. Using \'c\'." << std::endl;
		project->setLanguage(LanguageType::C);
	}
}

ParsingResult parseProject(int count, char* args[], Project* project) {
	ParsingResult results;
	results.outDir = "./";

	for (int i = count - 1; i > 0; i--) {
		string command = string(args[i]);
		if (command.find("--project-name=") != string::npos) {
			string projName = command.substr(command.find("=") + 1, string::npos);
			project->setName(projName);
			results.parsedValues |= Name;
		} else if (command.find("--project-version=") != string::npos) {
			string projVer = command.substr(command.find("=") + 1, string::npos);
			project->setVersion(projVer);
			results.parsedValues |= ProjVer;
		} else if (command.find("--project-language") != string::npos) {
			string language = command.substr(command.find("=") + 1, string::npos);
			parseLanguage(language, project);
			results.parsedValues |= ProjLang;
		} else if (command.find("--output-dir") != string::npos) {
			string outDir = command.substr(command.find("=") + 1, string::npos);
			results.outDir = outDir;
			results.parsedValues |= ProjDir;
		} else {
			cout << "Invalid command " << command << ". Aborting." << std::endl;
			exit(0);
		}
	}

	return results;
}

int createDirectory(const string& outDir, const string& dir, long permissions) {
	int status = 0;
	cout << "Creating " << dir << " directory" << std::endl;
	if ((status = mkdir((outDir + "/" + dir).c_str(), permissions)) == -1) {
		// TODO check error and display to user...
		cout << "ERROR CREATING DIR " << dir << std::endl;
	}
	return status;
}

void fillMakefile(ofstream& file, Project* project) {

	string compiler;
	string extension;
	string flags;

	switch (project->getLanguage()) {
		case LanguageType::C:
			compiler = "gcc";
			extension = "c";
			flags = "CFLAGS";
			break;
		case LanguageType::CPP:
			compiler = "g++";
			extension = "cpp";
			flags = "CPPFLAGS";
			break;
		default:
			break;
	}

	file << "VERSION = " << project->getVersion() << std::endl
		<< "PREFIX = lib" << std::endl
		<< "APP_NAME = " << project->getName() << std::endl
		<< "SUFIX = .so.$(VERSION)" << std::endl
		<< "BINARY_NAME = $(PREFIX)$(APP_NAME)$(SUFIX)" << std::endl
		<< "CC = " << compiler << std::endl << "SRC_EXTENSION = " << extension
		<< std::endl << ".SUFFIXES = .o .$(SRC_EXTENSION)" << std::endl
		<< "SHELL=/bin/bash"  << std::endl
		<< "SOURCES_DIR = $(shell cd ../src && dirs -l)" << std::endl
		<< "RESOURCES_DIR = $(shell cd ../resources && dirs -l)" << std::endl
		<< "SCRIPTS_DIR = $(shell cd ../scripts && dirs -l)" << std::endl
		<< "SOURCES = $(shell cd $(SOURCES_DIR) && ls *.$(SRC_EXTENSION))" << std::endl
		<< "CONFIG_NAME = debug" << std::endl
		<< "MODE = -g" << std::endl
		<< "OBJECTS = $(SOURCES:.$(SRC_EXTENSION)=.o)" << std::endl
		<< "OBJECTS_DIR = $(shell cd ../obj/$(CONFIG_NAME) && dirs -l)" << std::endl
		<< "INCLUDE_DIR = $(shell cd ../include && dirs -l)" << std::endl
		<< "DEPENDENCIES_DIR = $(shell cd ../dependencies && dirs -l)" << std::endl
		<< "DEP_INCLUDE_DIR = $(DEPENDENCIES_DIR)/include" << std::endl
		<< "DEP_LIBRARIES_DIR = $(DEPENDENCIES_DIR)/lib" << std::endl
		<< "BINARIES_DIR = $(shell cd ../bin/$(CONFIG_NAME) && dirs -l)" << std::endl
		<< "DEPENDENCIES = $(foreach dep, $(OBJECTS), $(OBJECTS_DIR)/$(dep))" << std::endl
		<< "SHARED_SRC_FLAGS = -fPIC" << std::endl
		<< flags << " = $(MODE) $(SHARED_SRC_FLAGS) -Wall -I$(INCLUDE_DIR) -I$(DEP_INCLUDE_DIR)" << std::endl
		<< "SHARED_LIB_FLAGS = -shared -Wl,-soname,$(BINARY_NAME)" << std::endl
		<< "LDFLAGS = -L$(DEP_LIBRARIES_DIR) $(SHARED_LIB_FLAGS)" << std::endl
		<< "CLEAN_COMMAND = rm -f *.o *~ *.bak $(BINARY_NAME)" << std::endl
		<< std::endl
		<< ".$(SRC_EXTENSION).o:" << std::endl
		<< "\t$(CC) $(" << flags << ") -c $< -o $@" << std::endl
		<< std::endl
		<< "all:" << std::endl
		<< "\t-@ make create-links" << std::endl
		<< "\t-@ make $(BINARIES_DIR)/$(BINARY_NAME)" << std::endl
		<< "\t-@ make remove-links" << std::endl
		<< std::endl
		<< "create-links:" << std::endl
		<< "\t$(foreach file, $(SOURCES), $(shell ln -fs $(SOURCES_DIR)/$(file) $(OBJECTS_DIR)/$(file)))" << std::endl
		<< std::endl
		<< "remove-links:" << std::endl
		<< "\t-@ rm -rf $(OBJECTS_DIR)/*.$(SRC_EXTENSION)" << std::endl
		<< std::endl
		<< "$(BINARIES_DIR)/$(BINARY_NAME): $(DEPENDENCIES)" << std::endl
		<< "\t$(CC) $(DEPENDENCIES) $(LDFLAGS) -o $(BINARIES_DIR)/$(BINARY_NAME)" << std::endl
		<< std::endl
		<< "clean:" << std::endl
		<< "\t-@ make clean-all" << std::endl
		<< "\t-@ make clean-all SUFIX=" << std::endl
		<< "\t-@ make clean-all SUFIX=.a" << std::endl
		<< std::endl
		<< "clean-all:" << std::endl
		<< "\t-@ $(CLEAN_COMMAND)" << std::endl
		<< "\t-@ cd ../ && $(CLEAN_COMMAND)" << std::endl
		<< "\t-@ cd ../bin/ && $(CLEAN_COMMAND)" << std::endl
		<< "\t-@ make clean_bin CONFIG_NAME=release" << std::endl
		<< "\t-@ make clean_bin CONFIG_NAME=release SUFIX=.a" << std::endl
		<< "\t-@ make clean_bin CONFIG_NAME=debug" << std::endl
		<< "\t-@ make clean_bin CONFIG_NAME=debug SUFIX=.a" << std::endl
		<< "\t-@ cd ../obj/debug/ && $(CLEAN_COMMAND)" << std::endl
		<< "\t-@ cd ../obj/release/ && $(CLEAN_COMMAND)" << std::endl
		<< "\t-@ cd ../src/ && $(CLEAN_COMMAND)" << std::endl
		<< "\t-@ cd ../include/ && $(CLEAN_COMMAND)" << std::endl
		<< "\t-@ make remove-links CONFIG_NAME=release" << std::endl
		<< "\t-@ make remove-links CONFIG_NAME=debug" << std::endl
		<< std::endl
		<< "clean_bin:" << std::endl
		<< "\t-@ cd ../bin/debug/ && $(CLEAN_COMMAND)" << std::endl
		<< "\t-@ cd ../bin/release/ && $(CLEAN_COMMAND)" << std::endl
		<< std::endl
		<< "release:" << std::endl
		<< "\t-@ make depend CONFIG_NAME=release SHARED_LIB_FLAGS= SHARED_SRC_FLAGS= SUFIX= PREFIX=" << std::endl
		<< "\t-@ make CONFIG_NAME=release MODE=-O3 SHARED_LIB_FLAGS= SHARED_SRC_FLAGS= SUFIX= PREFIX=" << std::endl
		<< std::endl
		<< "debug:" << std::endl
		<< "\t-@ make depend CONFIG_NAME=debug SHARED_LIB_FLAGS= SHARED_SRC_FLAGS= SUFIX=" << std::endl
		<< "\t-@ make CONFIG_NAME=debug MODE=-g SHARED_LIB_FLAGS= SHARED_SRC_FLAGS= SUFIX=" << std::endl
		<< std::endl
		<< "lib-release:" << std::endl
		<< "\t-@ make static-lib-release" << std::endl
		<< "\t-@ make shared-lib-release" << std::endl
		<< std::endl
		<< "lib-debug:" << std::endl
		<< "\t-@ make static-lib-debug" << std::endl
		<< "\t-@ make shared-lib-debug" << std::endl
		<< std::endl
		<< "static-lib: $(DEPENDENCIES)" << std::endl
		<< "\t-@ rm -f $(BINARIES_DIR)/$(BINARY_NAME)" << std::endl
		<< "\tar rcs $(BINARIES_DIR)/$(BINARY_NAME) $(DEPENDENCIES)" << std::endl
		<< std::endl
		<< "static-lib-release:" << std::endl
		<< "\t-@ make depend CONFIG_NAME=release" << std::endl
		<< "\t-@ make create-links CONFIG_NAME=release" << std::endl
		<< "\t-@ make static-lib CONFIG_NAME=release MODE=-O3 SUFIX=.a SHARED_LIB_FLAGS=" << std::endl
		<< "\t-@ make remove-links CONFIG_NAME=release" << std::endl
		<< std::endl
		<< "static-lib-debug:" << std::endl
		<< "\t-@ make depend CONFIG_NAME=debug" << std::endl
		<< "\t-@ make create-links CONFIG_NAME=debug" << std::endl
		<< "\t-@ make static-lib CONFIG_NAME=debug MODE=-g SUFIX=.a SHARED_LIB_FLAGS=" << std::endl
		<< "\t-@ make remove-links CONFIG_NAME=debug" << std::endl
		<< std::endl
		<< "shared-lib-release:" << std::endl
		<< "\t-@ make depend CONFIG_NAME=release" << std::endl
		<< "\t-@ make CONFIG_NAME=release MODE=-O3" << std::endl
		<< std::endl
		<< "shared-lib-debug:" << std::endl
		<< "\t-@ make depend CONFIG_NAME=debug" << std::endl
		<< "\t-@ make CONFIG_NAME=debug MODE=-g" << std::endl
		<< std::endl
		<< "depend:" << std::endl
		<< "\t-@ cd $(SOURCES_DIR) && makedepend -Y *.$(SRC_EXTENSION) -I$(INCLUDE_DIR) -p$(OBJECTS_DIR)/ -f$(SCRIPTS_DIR)/Makefile" << std::endl;
}

void fillMainFile(ofstream& file, Project* project) {
	file << "int main(int argc, char* args[]) {" << std::endl
		<< "\treturn 0;" << std::endl << "}" << std::endl;
}

void createDirectoryHierachy(Project* project, const string& outDir) {
	// TODO POSIX code, doesn't compile under windows...
	struct stat info;

	if (stat(outDir.c_str(), &info) == -1) {
		// TODO check error and display to user...
		cout << "ERROR ON OUTDIR " << outDir << std::endl;
	}

	if (S_ISDIR(info.st_mode)) {
		if (info.st_mode & S_IWUSR) {
			// TODO refactor this code!!!
			// creates under 755 permission modes
			long permissions = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP
				| S_IROTH | S_IXOTH;
			for (int i = 0; i < 12; i++)
				createDirectory(outDir, dirs[i], permissions);
			// TODO start creating Makefile
			ofstream makeFile((outDir + "/scripts/Makefile").c_str());
			if (makeFile.fail()) {
				cout << "AN ERROR OCCURRED WHEN TRYING TO CREATE MAKEFILE" << std::endl;
				return;
			}
			fillMakefile(makeFile, project);

			makeFile.close();

			string extension;

			switch (project->getLanguage()) {
				case LanguageType::C:
					extension = "c";
					break;
				case LanguageType::CPP:
					extension = "cpp";
					break;
			}

			ofstream mainFile((outDir + "/src/main." + extension).c_str());
			if (mainFile.fail()) {
				cout << "AN ERROR OCCURRED WHEN TRYING TO CREATE MAIN FILE." << std::endl;
				return;
			}
			fillMainFile(mainFile, project);

			mainFile.close();
		} else {
			// TODO user doesn't have write permissions;
			cout << "USER WITHOUT PERMISSIONS ON " << outDir << std::endl;
		}
	} else {
		// TODO handle this...
		cout << "OUTPUT DIR IS NOT A DIRECTORY" << std::endl;
	}
}

int main(int count, char* args[]) {

	if (count == 1) {
		showUsage();
		return 0;
	}

	Project* project = new Project("");

	ParsingResult results = parseProject(count, args, project);

	if (results.parsedValues & Name) {
		createDirectoryHierachy(project, results.outDir);
	} else {
		cout << "You must at least provide a project name." << std::endl;
	}

	delete project;

	return 0;
}
